/* WindowsFormsGraphics.h
 *
 *	Copyright (C) 2008 CRIMERE
 *	Copyright (C) 2008 Jean-Marc Mercier
 *	
 *	This file is part of OTS (optimally transported schemes), an open-source library
 *	dedicated to scientific computing. http://code.google.com/p/optimally-transported-schemes/
 *
 *	CRIMERE makes no representations about the suitability of this
 *	software for any purpose. It is provided "as is" without express or
 *	implied warranty.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

#ifndef _WindowsFormsGraphics_
#define _WindowsFormsGraphics_

typedef smart_ptr<Graphical_Interface>		interface_type;
static interface_type interface_;


template <class MS_String = System::String>
struct std_string_to_string {
	MS_String^ operator()(const std::string & _string) {
					std::string::size_type _size = _string.size();
					MS_String^ test_string("");
					for(std::string::size_type i=0; i<_size; ++i) {
						wchar_t merde(_string[i]);
						MS_String^ local_string(System::Convert::ToString(merde));
						test_string = System::String::Concat(test_string,local_string);
					};
					return test_string;
	};
};

template <class MS_String = System::String>
struct string_to_std_string {
	std::string operator()(MS_String^ _string) {
					IEnumerator^ OperandEnum = _string->GetEnumerator();
					std::string _return; 
					while ( OperandEnum->MoveNext() )
					   {
						   _return += System::Convert::ToChar (OperandEnum->Current);
					   }
					
					return _return;
	};
};


template <class MS_tree = System::Windows::Forms::TreeView>
struct create_tree {
	MS_tree^ operator()() 
	{
		MS_tree^ tree = gcnew System::Windows::Forms::TreeView();
		numerical_schemes_list_type::cauchy_list_iterator_type _begin = Graphical_Interface::instance().cauchy_list_.begin(), _end = Graphical_Interface::instance().cauchy_list_.end();
		for (;_begin != _end; ++_begin) {
			System::String^ name = std_string_to_string<>()( _begin->first );
			System::Windows::Forms::TreeNode^  treeNode = (gcnew System::Windows::Forms::TreeNode(name));
			treeNode->Text = name;

			tree->Nodes->Add(treeNode);
		}
		return tree;
	};
};


	template <class particle_list_type>
	class BoundBox 
	{
	public:
		typedef particle_list_type 							particle_list_type;
		typedef typename particle_list_type::particle_type	particle_type;
		typedef typename particle_type::value_type			value_type;

		BoundBox(const particle_list_type & S) :
		bound_sup(2),
		bound_inf(2),
		bound_abs(2),
		average(2),
		size_(S.size())
		{
				assert(S.dim() == 2);
				if (S.size()) {
					average = jmmath::moment1(S);
					bound_sup = S.sup_bound();
					bound_inf = S.inf_bound();
				}
				else {
					bound_sup.assign(1.);
					bound_inf.assign(0.);
					average.assign(0.5);
				};
				bound_abs = bound_sup - bound_inf;
				if (bound_abs[0] < 0.001)bound_abs[0] = 0.001;
				if (bound_abs[1] < 0.001)bound_abs[1] = 0.001;
		};
		particle_type bound_sup, bound_inf,bound_abs;
		particle_type average;
		OS_size size_;
	};

	template <class windows_graphic,class data>
	public struct draw_graph_with_axis
	{
		typedef BoundBox<data> BoundBox_type;
		typedef typename BoundBox_type::particle_list_type particle_list_type;
		typedef typename BoundBox_type::particle_type particle_type;

		draw_graph_with_axis(const data& data) :
		BoundBox_(data),
		data_(data),DrawAxes_(true),
		manual_scale_x_(2),manual_scale_y_(2),manual_scale_abs_(2),autoscale_(true)
		{
			manual_scale_x_.assign(0.);
			manual_scale_y_.assign(0.);
			manual_scale_abs_.assign(0.);
		};
		void toggle_auto_scale() {autoscale_ = !autoscale_;};
		void set_manual_scale(float x_inf, float x_sup, float y_inf, float y_sup)
		{
			autoscale_ = false;
			if (x_sup - x_inf < 0.0001) 
			{
				manual_scale_abs_[0] = BoundBox_.bound_abs[0];
				manual_scale_x_ = BoundBox_.bound_inf;
			}
			else
			{
				manual_scale_abs_[0] = x_sup - x_inf;
				manual_scale_x_[0] = x_inf;
				manual_scale_x_[1] = x_sup;
			};
			if (y_sup - y_inf < 0.0001) {
				manual_scale_abs_[1] = BoundBox_.bound_abs[1];
				manual_scale_y_ = BoundBox_.bound_sup;
			}
			else
			{
				manual_scale_abs_[1] = y_sup - y_inf;
				manual_scale_y_[0] = y_inf;
				manual_scale_y_[1] = y_sup;
			};
		};
		void scale(windows_graphic ^graphic) {
			if (autoscale_ == true) 
				auto_scale(graphic);
			else
				manual_scale(graphic, manual_scale_x_[0],manual_scale_x_[1],manual_scale_y_[0],manual_scale_y_[1]);
		};
		void auto_scale(windows_graphic ^graphic) {
			  //resizing to graph cooridnates
/*		  graphic->ResetTransform();
		  System::Drawing::RectangleF Bound = graphic->ClipBounds;
		  graphic->ScaleTransform( (float) ((Bound.Right-Bound.Left)/(BoundBox_.bound_abs[0]*1.1)), (float)((Bound.Top-Bound.Bottom)/(BoundBox_.bound_abs[1]*1.1))  );
		  graphic->TranslateTransform( (float) (BoundBox_.bound_abs[0]*.05 - BoundBox_.bound_inf[0]), (float) (-BoundBox_.bound_abs[1]*.05-BoundBox_.bound_sup[1]));
*/
			manual_scale(graphic,BoundBox_.bound_inf[0],BoundBox_.bound_sup[0],BoundBox_.bound_inf[1],BoundBox_.bound_sup[1]);
		};
		void manual_scale(windows_graphic ^graphic, float x_inf, float x_sup, float y_inf, float y_sup)
		{
			float x_abs = x_sup-x_inf;
			float y_abs = y_sup-y_inf;
		  graphic->ResetTransform();
		  System::Drawing::RectangleF Bound = graphic->ClipBounds;
		  graphic->ScaleTransform( (float) ((Bound.Right-Bound.Left)/(x_abs*1.1)), (float)((Bound.Top-Bound.Bottom)/(y_abs*1.1))  );
		  graphic->TranslateTransform( (float) (x_abs*.05 - x_inf), (float) (-y_abs*.05-y_sup));
		};
		void operator()(windows_graphic ^graphic) // draw data 
		{
		  if (data_.size() == 0) return;
		  scale(graphic);

		  height_x_ = (float) ( BoundBox_.bound_abs[0]/ std::sqrt((OS_double) data_.size())/10.);
		  height_x_ = height_x_ < (float) (BoundBox_.bound_abs[0]/300.) ? (float) (BoundBox_.bound_abs[0]/300.) : height_x_; 
		  height_y_ = (float) ( BoundBox_.bound_abs[1]/ std::sqrt((OS_double) data_.size())/10.);
		  height_y_ = height_y_ < (float) (BoundBox_.bound_abs[1]/300.) ? (float) (BoundBox_.bound_abs[1]/300.) : height_y_; 

		  particle_list_type::const_iterator begin_(data_.begin()), end_(data_.end());
		  for (;begin_ != end_;begin_++) {
			  //draw small ellipses
			  graphic->FillEllipse(System::Drawing::Brushes::Blue,( (float) ((*begin_)[0]) )-height_x_/ ( (float) 2.),(float)((*begin_)[1])-height_y_/( (float) 2.),height_x_ ,height_y_ );
		  };
		  DrawAxes(graphic);
		};
		void DrawAxes(windows_graphic ^graphic) {
				if (!DrawAxes_) return;
				scale(graphic);

				width_ = Get_Pen_Width();
				System::Drawing::Pen black_pen(System::Drawing::Color::Black,width_);
				graphic->DrawLine( %black_pen, (float) BoundBox_.bound_inf[0], (float) BoundBox_.average[1],
					 (float) BoundBox_.bound_sup[0], (float) BoundBox_.average[1] );

				System::Drawing::Pen black_pen_2(System::Drawing::Color::Black,width_);
				graphic->DrawLine( %black_pen_2, (float) BoundBox_.average[0], (float) BoundBox_.bound_inf[1], 
					 (float) BoundBox_.average[0], (float) BoundBox_.bound_sup[1]);

				System::Drawing::Font myFont(L"Times New Roman", 10);

				graphic->ResetTransform();

				System::Drawing::RectangleF Bound = graphic->ClipBounds;
				float height_x = (Bound.Right-Bound.Left)/20;
				float height_y = (Bound.Bottom-Bound.Top)/20;


				graphic->DrawString(System::Convert::ToString(System::Decimal::Round( (System::Decimal) BoundBox_.bound_inf[0],2) ), %myFont, System::Drawing::Brushes::Black, 
					Bound.Left, (Bound.Bottom-Bound.Top)/2);

				graphic->DrawString(System::Convert::ToString(System::Decimal::Round( (System::Decimal) BoundBox_.bound_inf[1],2) ), %myFont, System::Drawing::Brushes::Black, 
					(Bound.Right+Bound.Left)/2, Bound.Bottom-height_y);

				graphic->DrawString(System::Convert::ToString(System::Decimal::Round( (System::Decimal) BoundBox_.bound_sup[0],2) ), %myFont, System::Drawing::Brushes::Black, 
					Bound.Right-height_x, (Bound.Top + Bound.Bottom)/2 );

				graphic->DrawString(System::Convert::ToString(System::Decimal::Round( (System::Decimal) BoundBox_.bound_sup[1],2) ), %myFont, System::Drawing::Brushes::Black, 
					(Bound.Right+Bound.Left)/2, Bound.Top);

		};
		float Get_Pen_Width() 
		{
			width_ = (float) ( (std::min(BoundBox_.bound_abs[0],BoundBox_.bound_abs[1])/500.));
			return width_;
		};


		BoundBox_type BoundBox_;
		const data& data_;
		bool DrawAxes_;
		bool autoscale_;
		float width_;
		float height_x_;
		float height_y_;
		particle_type manual_scale_x_, manual_scale_y_,manual_scale_abs_;
	};


#endif
